﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace Galaktika.BI.Silverlight.ClientApp.Web.Runtime.Services
{
    using Galaktika.BI.Extensibility.Dom;

    public class ScriptEngine
    {
        private static Regex m_FuncRegex;
        private static Regex FuncRegex
        {
            get
            {
                if (m_FuncRegex == null)
                {
                    m_FuncRegex = new Regex("([^\\(]*)\\((.*)\\)[^\\)]*", RegexOptions.CultureInvariant);
                }

                return m_FuncRegex;
            }
        }

        private static string[] GetParams(string paramsString)
        {
            paramsString = paramsString.TrimStart(' ').TrimEnd(' ');
            List<string> res = new List<string>();
            while (paramsString.Length > 0)
            {
                bool passNextQuots = false;
                bool firstQuotsPassed = false;
                StringBuilder val = new StringBuilder();
                int pos = 0;
                while (true)
                {
                    if (paramsString[pos] == '"')
                    {
                        if (firstQuotsPassed == true)
                        {
                            if (passNextQuots)
                            {
                                passNextQuots = false;
                                val.Append('"');
                            }
                            else
                            {
                                pos++;
                                break;
                            }
                        }
                        else
                        {
                            firstQuotsPassed = true;
                        }
                    }
                    else
                    {
                        if (paramsString[pos] == '\\' && paramsString[pos + 1] == '"')
                        {
                            passNextQuots = true;
                        }
                        else
                        {
                            val.Append(paramsString[pos]);
                        }
                    }
                    pos++;
                    if (pos >= paramsString.Length) break;
                }

                // перемещаемся к следующему параметру
                while (pos < paramsString.Length && paramsString[pos] != '"') pos++;

                //удаляем параметр
                if (pos >= paramsString.Length) pos = paramsString.Length;
                paramsString = paramsString.Remove(0, pos);

                res.Add(val.ToString());
            }

            return res.ToArray();
        }

        public static FuncInvokeCheckResult CheckFuncInvoke(string str)
        {
            Match m = FuncRegex.Match(str);
            if (m.Success && m.Groups.Count == 3)
            {
                FuncInvokeCheckResult res = new FuncInvokeCheckResult(true);
                res.FunctionType = m.Groups[1].Value;
                res.Parameters = GetParams(m.Groups[2].Value);
                return res;
            }

            return new FuncInvokeCheckResult(false);
        }

        const string ACTIONS_CLASS_NAME = "Actions";
        const string ACTION_INVOKE_METHOD_NAME = "Invoke";

        public static string InvokeFunction(IBindingContext context, FuncInvokeCheckResult check, Assembly targetAssembly)
        {
            FieldInfo caller = GetCaller(targetAssembly);
            if (caller == null)
            {
                throw new TargetInvocationException(Localization.Exc_CallerNotFound, null);
            }

            caller.SetValue(null, new MockBindableObject(context));

            Type type = targetAssembly.GetType(check.FunctionType, false);
            if (type != null)
            {
                MethodInfo invoke = type.GetMethod(ACTION_INVOKE_METHOD_NAME,
                    BindingFlags.Public | BindingFlags.Static);
                if (invoke != null)
                {
                    object[] args = new object[check.Parameters.Length];
                    ParameterInfo[] realParameters = invoke.GetParameters();
                    if (args.Length != realParameters.Length)
                    {
                        throw new TargetParameterCountException(string.Format(Localization.Exc_ParamCountMismatch, check.FunctionType));
                    }
                    for (int i = 0; i < args.Length; i++)
                    {
                        try
                        {
                            args[i] = Convert.ChangeType(check.Parameters[i], realParameters[i].ParameterType, CultureInfo.CurrentCulture);
                        }
                        catch (InvalidCastException)
                        {
                        }
                    }
                    object res = invoke.Invoke(null, args);
                    if (res != null)
                    {
                        return res.ToString();
                    }
                }
            }

            return string.Empty;
        }

        const string GLOBAL_NAMESPACE = "Global";
        const string CURRENT_CLASS_NAME = "Current";
        const string CALLER_FIELD_NAME = "Caller";
        const string BINDING_CONTEXT_FIELD_NAME = "BindingContext";

        private static FieldInfo GetCaller(Assembly assembly)
        {
            Type type = assembly.GetType(
                string.Format("{0}.{1}", GLOBAL_NAMESPACE, CURRENT_CLASS_NAME), true);
            return type.GetField(CALLER_FIELD_NAME, BindingFlags.Public | BindingFlags.Static);
        }

        private class MockBindableObject //: Galaktika.BI.Runtime.Services.IBindableObject
        {
            public MockBindableObject(IBindingContext realContext)
            {
                m_Context = realContext;
            }

            #region IBindableObject Members

            private IBindingContext m_Context;
            public IBindingContext Context
            {
                get
                {
                    return m_Context;
                }
                set
                {
                    m_Context = value;
                }
            }

            private SafeDictionary<string, IBindingManager> m_Bindings = new SafeDictionary<string, IBindingManager>();
            public IDictionary<string, IBindingManager> Bindings
            {
                get
                {
                    return m_Bindings;
                }
            }

            #endregion
        }
    }

    [XmlRoot(Namespace = Galaktika.BI.Silverlight.Common.Namespace)]
    public class ArgumentEntry
    {
        public ArgumentEntry()
        {
        }

        public ArgumentEntry(string name, string value)
        {
            this.Name = name;
            this.Value = value;
        }

        public string Name { get; set; }
        public string Value { get; set; }
    }
}
